package logic;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.Scanner;
import java.util.Vector;

import vis.KeyPanel;

import logic.events.Append;
import logic.events.Block;
import logic.events.CipherEvent;
import logic.events.Clear;
import logic.events.Column;
import logic.events.Pause;
import logic.events.Reset;
import logic.events.Row;
import logic.events.SetGray;

public class PlayfairCipher {
	private Language lang = null;
	private PlayfairKey key = null;
	private int pause0 = 1000, pause1 = 1000, pause2 = 1000;
	
	private Vector<CipherEvent> events = new Vector<CipherEvent>();
	
	public PlayfairCipher(Language lang)
	{
		this.lang = lang;
	}
	
	public PlayfairCipher(Language lang, PlayfairKey key)
	{
		this.lang = lang;
		this.key = key;
	}
	
	public PlayfairCipher(Language lang, String keyword)
	{
		this.lang = lang;
		setKey(keyword);
	}
	
	public void setKey(PlayfairKey key)
	{
		this.key = key;
	}
	
	public void setKey(String keyword)
	{
		if(lang == null) return;
		this.key = new PlayfairKey(sanitizeString(keyword), lang);
	}
	
	public PlayfairKey getKey()
	{
		return key;
	}
	
	public Language getLanguage()
	{
		return lang;
	}
	
	public String sanitizeString(String str)
	{
		if(lang == null) return null;
		String res = new String();
		for(int i = 0; i < str.length(); ++i)
		{
			String let = str.substring(i, i + 1);
			let = let.toLowerCase();
			if(Character.isLowerCase(let.charAt(0)))
			{
				let = lang.doMappings(let);
				let = let.substring(0, 1);
				if(lang.isValidLetter(let))
					res += let;
			}
		}
		return res;
	}
	
	public String cipherPair(String str)
	{
		if(lang == null || key == null || str.length() < 2) return null;
		String c1 = str.substring(0, 1);
		String c2 = str.substring(1, 2);
		
		int n1 = key.findLetter(c1);
		int n2 = key.findLetter(c2);
		
		if(n1 == -1 || n2 == -1) return null; // invalid char
		
		int x1 = n1 % 5, y1 = n1 / 5, x2 = n2 % 5, y2 = n2 / 5;
		int ox1 = x1, ox2 = x2, oy1 = y1, oy2 = y2;
		
		if(x1 == x2)
		{
			y1 = (y1 + 1) % 5;
			y2 = (y2 + 1) % 5;
			events.add(new Column(ox1, oy1, oy2, 0, false));
			events.add(new Append(c1, key.getLetter(x1, y1)));
			events.add(new Pause(pause2));
			events.add(new Column(ox1, oy1, oy2, 1, false));
			events.add(new Append(c2, key.getLetter(x2, y2)));
		}
		else if(y1 == y2)
		{
			x1 = (x1 + 1) % 5;
			x2 = (x2 + 1) % 5;
			events.add(new Row(y1, ox1, ox2, 0, false));
			events.add(new Append(c1, key.getLetter(x1, y1)));
			events.add(new Pause(pause2));
			events.add(new Row(y1, ox1, ox2, 1, false));
			events.add(new Append(c2, key.getLetter(x2, y2)));
		}
		else
		{
			int tmp;
			tmp = x1;
			x1 = x2;
			x2 = tmp;
			events.add(new Block(x2, y1, x1, y2, 0));
			events.add(new Append(c1, key.getLetter(x1, y1)));
			events.add(new Pause(pause2));
			events.add(new Block(x2, y1, x1, y2, 1));
			events.add(new Append(c2, key.getLetter(x2, y2)));
		}
		events.add(new Pause(pause2));
		events.add(new Clear());
		events.add(new Pause(pause1));
		return key.getLetter(x1, y1) + key.getLetter(x2, y2);
	}
	
	public String decipherPair(String str)
	{
		if(lang == null || key == null || str.length() < 2) return null;
		String c1 = str.substring(0, 1);
		String c2 = str.substring(1, 2);
		
		int n1 = key.findLetter(c1);
		int n2 = key.findLetter(c2);
		
		if(n1 == -1 || n2 == -1) return null; // invalid char
		
		int x1 = n1 % 5, y1 = n1 / 5, x2 = n2 % 5, y2 = n2 / 5;
		int ox1 = x1, ox2 = x2, oy1 = y1, oy2 = y2;
		
		if(x1 == x2)
		{
			y1 = (y1 - 1); if(y1 < 0) y1 += 5;
			y2 = (y2 - 1); if(y2 < 0) y2 += 5;
			events.add(new Append(c1 + c2, ""));
			events.add(new Column(ox1, oy1, oy2, -1, false));
			events.add(new Pause(pause0));
			events.add(new Column(ox1, oy1, oy2, 0, false));
			events.add(new Append("", key.getLetter(x1, y1)));
			events.add(new Pause(pause2));
			events.add(new Column(ox1, oy1, oy2, 1, false));
			events.add(new Append("", key.getLetter(x2, y2)));
		}
		else if(y1 == y2)
		{
			x1 = (x1 - 1); if(x1 < 0) x1 += 5;
			x2 = (x2 - 1); if(x2 < 0) x2 += 5;
			events.add(new Append(c1 + c2, ""));
			events.add(new Row(y1, ox1, ox2, -1, false));
			events.add(new Pause(pause0));
			events.add(new Row(y1, ox1, ox2, 0, false));
			events.add(new Append("", key.getLetter(x1, y1)));
			events.add(new Pause(pause2));
			events.add(new Row(y1, ox1, ox2, 1, false));
			events.add(new Append("", key.getLetter(x2, y2)));
		}
		else
		{
			int tmp;
			tmp = x1;
			x1 = x2;
			x2 = tmp;
			events.add(new Append(c1 + c2, ""));
			events.add(new Block(x2, y1, x1, y2, -1));
			events.add(new Pause(pause0));
			events.add(new Block(x2, y1, x1, y2, 0));
			events.add(new Append("", key.getLetter(x1, y1)));
			events.add(new Pause(pause2));
			events.add(new Block(x2, y1, x1, y2, 1));
			events.add(new Append("", key.getLetter(x2, y2)));
		}
		events.add(new Pause(pause2));
		events.add(new Clear());
		events.add(new Pause(pause1));
		return key.getLetter(x1, y1) + key.getLetter(x2, y2);
	}
	
	public String cipherString(String str)
	{
		String res = new String();
		str = sanitizeString(str);
		str = lang.doMappings(str);
		
		events.clear();
		events.add(new Reset());
		events.add(new SetGray(str.toUpperCase()));
		
		for(int i = 0; i < str.length(); i += 2)
		{
			res += cipherPair(str.substring(i, i + 2));
		}
		
		return res;
	}
	
	public void cipherFile(String in, String out)
	{
		File fin = new File(in);
		File fout = new File(out);
		
		if(key == null) { System.out.println("No key is set!"); return; }
		
		Scanner scn = null;
		try {
			scn = new Scanner(fin);
		} catch (FileNotFoundException e) {
			System.out.println("Can't open file : " + in);
			return;
		}
		
		PrintStream ps = null;
		try {
			ps = new PrintStream(fout);
		} catch (FileNotFoundException e) {
			System.out.println("Can't open file : " + out);
			return;
		}
		
		while(scn.hasNext())
		{
			ps.println(cipherString(scn.nextLine()));
		}
		
		ps.close();
		scn.close();
	}
	
	public void decipherFile(String in, String out)
	{
		File fin = new File(in);
		File fout = new File(out);
		
		Scanner scn = null;
		try {
			scn = new Scanner(fin);
		} catch (FileNotFoundException e) {
			System.out.println("Can't open file : " + in);
			return;
		}
		
		PrintStream ps = null;
		try {
			ps = new PrintStream(fout);
		} catch (FileNotFoundException e) {
			System.out.println("Can't open file : " + out);
			return;
		}
		
		while(scn.hasNext())
		{
			ps.println(decipherString(scn.nextLine()));
		}
		
		ps.close();
		scn.close();
	}
	
	public void playback(final KeyPanel panel, final int timeout)
	{
		if(events.size() == 0) return;
		
		Thread tr = new Thread(new Runnable() {
			
			@Override
			public void run() {
				try {
					Thread.sleep(timeout);
				} catch (InterruptedException e) {}
				for(CipherEvent ce : events)
				{
					ce.display(panel);
				}
			}
		});
		tr.start();
	}
	
	public String decipherString(String str)
	{
		if(str.length() == 0 || str.length() % 2 != 0)
			return "";
		String res = new String();
		//str = sanitizeString(str);
		
		events.clear();
		events.add(new Reset());
		events.add(new SetGray(str.toUpperCase()));
		
		for(int i = 0; i < str.length(); i += 2)
		{
			res += decipherPair(str.substring(i, i + 2));
		}
		
		return res;
	}
	
	public void showcase(String text)
	{
		System.out.println("Key :");
		System.out.println(getKey());
		System.out.println("Open text : " + text);
		String cipher = cipherString(text);
		System.out.println("Cipher    : " + cipher);
		System.out.println("Decipher  : " + decipherString(cipher));
	}
}
